/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.search.strategy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.groupme.model.Group;
import net.groupme.model.MODEL_CONSTANTS;
import net.groupme.model.ObjectFactory;
import net.groupme.model.Resource;
import net.groupme.model.Tag;
import net.groupme.search.strategy.model.WeightedGroup;
import net.groupme.search.strategy.model.WeightedResource;
import net.groupme.search.strategy.model.WeightedIdentifiableComparatorByWeight;

/**
 * created on Jul 3, 2007
 * 
 * This implements the {@link SearchStrategy} interface in an improved way. It 
 * uses a ranking function to weight suiting resources/groups.
 * 
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.5 $ $Date: 2007-07-06 15:07:07 $
 */
public class SearchBasedOnGroupContext implements SearchStrategy {
	
	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());
	
	/** the strategy which builds the base to compute scorings */
	private ScoringStrategy scoringStrategy = null;
	
	/**
	 * 
	 * Creates a new SearchBasedOnGroupContext instance.
	 * @param scoringStrategy the scoring strategy (how to measure scoring values of resources 
	 * according to tags and other context information)
	 */
	public SearchBasedOnGroupContext(ScoringStrategy scoringStrategy){
		this.scoringStrategy = scoringStrategy;
	}

	/**
	 * This method returns the scoringStrategy of a SearchBasedOnGroupContext object.
	 * @return the scoringStrategy = object which defines how to measure scoring values of resources 
	 * according to tags and other context information
	 */
	public ScoringStrategy getScoringStrategy() {
		return scoringStrategy;
	}

	/**
	 * This method sets the scoringStrategy of a SearchBasedOnGroupContext object.
	 * @param scoringStrategy the scoringStrategy defines how to measure scoring values of resources 
	 * according to tags and other context information
	 */
	public void setScoringStrategy(ScoringStrategy scoringStrategy) {
		this.scoringStrategy = scoringStrategy;
	}

	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.SearchStrategy#searchForGroups(java.util.Collection, boolean)
	 */
	public List<Group> searchForGroups(Collection<Tag> tags, boolean allTags) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * This method searches for resources that are realted to the given tags. For a
	 * single tag the search sequence is defined as follows:
	 * <ol>
	 * 	<li>Determine resources that are directly tagged with the given tag: <resource, number of users that taggeed the resource with the tag</li>
	 *  <li>Determine groups that are directly tagged with the given tag: <group, number of users that taggeed the group with the tag</li>
	 *  <ol>
	 *  	<li>Determine all resources (also those from the first step) within the groups</li>
	 *  	<li>weight those resources according to a ranking strategy</li>
	 *  </ol>
	 *  <li>Combine results from 1. and 2. and return a ranked list of suiting resources.</li>
	 * </ol>
	 * @param tags the tags to search for 
	 * @param allTags and-connected search
	 * @param noGroups as {@link Group} instances are also {@link Resource} one can explicitly exclude {@link Group} instances by
	 * if this parameter is <code>false</code>
	 * @return suiting resources
	 * 
	 * TODO: testing, exclude groups if necessary
	 */
	public List<Resource> searchForResources(Collection<Tag> tags,
			boolean allTags, boolean noGroups) {
		//Map<Resource, Integer> directlyTaggedResources = getDirectlyTaggedResources(tags);
		
		Collection<List<WeightedResource>> scoringAccordingToTag = new ArrayList<List<WeightedResource>>();
		for(Tag tag: tags){
			scoringAccordingToTag.add(searchForResources(tag));
		}
		if(scoringAccordingToTag == null || scoringAccordingToTag.isEmpty()){
			return new ArrayList<Resource>();
		}
		//weight results (just make a sum):
		List<WeightedResource> overallScoring = new ArrayList<WeightedResource>();
		for(List<WeightedResource> weightedResources: scoringAccordingToTag){
			for(WeightedResource weightedResource: weightedResources){
				if(overallScoring.contains(weightedResource)){ //Note, that WeightedResource are compared, by their Resource and not by the weight-value!!!
					int pos = overallScoring.indexOf(weightedResource);
					overallScoring.get(pos).weight += weightedResource.weight;
				}
			}
		}
		Collections.sort(overallScoring, new WeightedIdentifiableComparatorByWeight());
		
		//put resources of ordered map into a list:
		List<Resource> resultList = new ArrayList<Resource>();
		for(int i=0; i<overallScoring.size(); i++){
			resultList.add(overallScoring.get(i).resource);
		}
		return resultList;
	}
	
	/**
	 * Performs a search based on a single tag.
	 * @param tag the tag the user is searching with
	 * @return a sorted map (mapping from suiting {@link Resource} and scoring value between 0..1)
	 */
	public List<WeightedResource> searchForResources(Tag tag){
		//1. (resource, number of users that used the given tag to describe a resource):
		List<WeightedResource> userWeightedResources = new ArrayList<WeightedResource>();
		for(Resource resource: tag.getResources()){
			userWeightedResources.add(new WeightedResource(resource, new Integer(tag.getUsers(resource).size()).doubleValue()));
		}
		
		logger.info("Number of suiting resources: " +  userWeightedResources.size());
		
		//2. (group, number of groups that used the given tag to describe the group):
		Collection<WeightedGroup> userWeightedGroups = new ArrayList<WeightedGroup>();
		for(Resource resource: tag.getResources()){
			if(MODEL_CONSTANTS.RESOURCE_TYPE_GROUP.equals(resource.getType())){
				userWeightedGroups.add(new WeightedGroup(ObjectFactory.getGroup(resource.getURI()), new Integer(tag.getUsers(resource).size()).doubleValue()));
			}
		}
		
		logger.info("Number of suiting groups: " +  userWeightedGroups.size());
		
		Map<WeightedGroup, List<WeightedResource>> resourcesOfGroup = new HashMap<WeightedGroup, List<WeightedResource>>();
		//2.b/c determine resources of suiting Groups and weight them
		for(WeightedGroup weightedGroup: userWeightedGroups){
			List<WeightedResource> weightedResources = new ArrayList<WeightedResource>();
			for(Resource resource: weightedGroup.group.getResources()){
				double score = this.scoringStrategy.computeResourceContextScore(resource, tag);
				weightedResources.add(new WeightedResource(resource, score));
			}
			resourcesOfGroup.put(weightedGroup, weightedResources);
		}
		
		List<WeightedResource> contextScoring = this.scoringStrategy.computeOverallContextScore(resourcesOfGroup);
		
		return this.scoringStrategy.combineScorings(userWeightedResources, contextScoring);
	}
	
	
	/**
	 * Returns a map with the resources that suit at least one of the given tags and a
	 * number of tags they suit.
	 * 
	 * @param tags the tags
	 * @return mapping between resource and number of given tags that are directly assign to the resource
	 */
	@SuppressWarnings("unused")
	private Map<Resource, Integer> getDirectlyTaggedResources(Collection<Tag> tags){
		Map<Resource, Integer> resourceAndNumberOfSuitingTags = new HashMap<Resource, Integer>();
		for(Tag tag: tags){
			Collection<Resource> resources = tag.getResources();
			for(Resource resource: resources){
				if(resourceAndNumberOfSuitingTags.containsKey(resource)){
					Integer numberOfSuitingTags = resourceAndNumberOfSuitingTags.get(resource) + 1;
					resourceAndNumberOfSuitingTags.put(resource, numberOfSuitingTags);
				}else{
					resourceAndNumberOfSuitingTags.put(resource, 1);
				}
			}
		}
		return resourceAndNumberOfSuitingTags;
	}

}
